import java.util.*;
import java.io.*;

// this class uses the code provided by Professor Michael J Clancy,
// in InstrumentedHashMap.java, as utilized in a lab exercise

public class HashTest {

	public static final int myTableSize = 1001;
	
	public static void generateStatistics (Iterator <Block> blockIterator, PrintStream counts) {
		
		// temporary array, which tallies up Blocks based on their hash codes
		int [ ] table = new int [myTableSize];
		// temporary variable to store the Blocks, as received
		Block currentBlock;
		// temporary variable to store the hash codes of the Blocks, as received
		int hashCode;
		
		// iterate through the Block iterator, incrementing necessary indices
		while (blockIterator.hasNext ( )) {
			currentBlock = blockIterator.next ( );
			hashCode = currentBlock.hashCode ( );
			// determine which index of the table to increment
			int index = (hashCode & 0x07FFFFFFF) % table.length;
			table[index]++;
		}
		
		// generate statistics
		int maxChainLength = table[0];
		int minChainLength = table[0];
		int elementCount = 0;
		int totalComparisons = 0;
		double avg;
		for (int k = 0; k < table.length; k++) {
			int chainLength = table[k];
			maxChainLength = (chainLength > maxChainLength) ? chainLength
					: maxChainLength;
			minChainLength = (chainLength < minChainLength) ? chainLength
					: minChainLength;
			elementCount += chainLength;
			// 1 + 2 + 3 + ... + chainLength = chainLength * (chainLength+1)/2
			totalComparisons += (chainLength * (chainLength + 1)) / 2;
		}
		avg = ((float) totalComparisons) / elementCount;
		double avgChainLength = ((double) elementCount) / table.length;

		double sqSum = 0;
		for (int k = 0; k < table.length; k++) {
			sqSum += (table[k] - avgChainLength) * (table[k] - avgChainLength);
		}

		System.out.println ("Number of different elements in the table = "
				+ elementCount);
		System.out.println ("Table size = " + table.length);
		System.out.println ("Maximum chain length = " + maxChainLength);
		System.out.println ("Minimum chain length = " + minChainLength);
		System.out.println ("Optimal chain length = "
				+ (elementCount + table.length - 1) / table.length);
		System.out.println ("Average # compares  = " + avg);
		System.out.println ("Standard deviation  = "
				+ Math.sqrt (sqSum / table.length));

		for (int k = 0; k < myTableSize; k++) {
			counts.println ("" + k + " " + table[k]);
		}
	}
	
	public static void main (String [ ] args) throws IOException {
		// this class requires two arguments -- the file containing the details of a tray to 
		// be generated, and the file to which the output must be sent, for the purpose
		// of plotting through GNUplot
		if (args.length != 2) {
			System.err.println ("Not enough arguments.");
			System.err.println ("Please enter either the file from which the tray configuration has to be generated,");
			System.err.println ("or the file to which the output of this class must be sent.");
			System.exit (1);
		}

		// set up a tempoary Scanner variable to construct the tray
		Scanner initialConfigFile = null;
		// set up an instance of this class
		HashTest hashingEfficacyCheck = new HashTest ( );
		
		// generate the tray -- uses code found in Solver.java
		// attempt to check for files for necessary input
		try {
			initialConfigFile = new Scanner (new File (args [0]));
		} catch (FileNotFoundException e) {
			System.out.println ("File(s) cannot be found.");
		}
		
		Integer trayLength = initialConfigFile.nextInt ( );
		Integer trayWidth = initialConfigFile.nextInt ( );
		
		// set up an initial tray
		Tray tray = new Tray ( );
		
		// set up blocks from the rest of initialConfigFile and add to the created tray
		while (initialConfigFile.hasNextInt ( )) {
			// set up a single block based on its length and width, and the row and
			// column of upper left corner, and add the block to the tray
			tray.addBlock (new Block (initialConfigFile.nextInt ( ), initialConfigFile.nextInt ( ),
									  initialConfigFile.nextInt ( ), initialConfigFile.nextInt ( )));
		}
		
		PrintStream counts = new PrintStream (new FileOutputStream (new File (args[1])));
		hashingEfficacyCheck.generateStatistics (tray.blockIterator ( ), counts);
		counts.close ( );
	}
}
